package serveur;

import java.io.*;
import java.net.*;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;

class Client extends Thread {

	private ServerSocket ss; // socket de service
	private MonitorSolver mSolver;
	private Joueur joueur;
	private static SortedSet<Joueur> listJoueurs = Collections.synchronizedSortedSet(new TreeSet<Joueur>());

	Client(ServerSocket S, MonitorSolver mSolver) throws IOException {
		ss = S;
		this.mSolver = mSolver;
	}

	/**
	 * Ajoute un client a notre liste (login + son flux d'ecriture)
	 * @param pw flux d'ecriture du client
	 * @param joueur login du client
	 * @return true si le joueur peut se connecter, false sinon
	 */
	private boolean connection(Joueur joueur) {
		if(!listJoueurs.contains(joueur)) {
			diffuser(joueur.getLogin() + " vient de nous rejoindre sur la partie.");
			listJoueurs.add(joueur);
			return true;
		}
		else
			return false;
	}

	/**
	 * Supprime un client a notre liste (login + son flux d'ecriture)
	 * @param pw flux d'ecriture du client
	 * @param joueur login du client
	 * @param raison raison de la deconnexion
	 */
	private void deconnection(Joueur joueur, String raison) {
		if(!raison.equals(""))
			joueur.envoyerMessage("Raison de votre deconnexion : " + raison);

		listJoueurs.remove(joueur);
		diffuser(joueur.getLogin() + " vient de nous quitter");
	}

	/**
	 * Supprime un client a notre liste (login + son flux d'ecriture)
	 * @param pw flux d'ecriture du client
	 * @param joueur login du client
	 */
	private void deconnection(Joueur joueur) {
		this.deconnection(joueur,"");
	}

	/**
	 * Diffuse un message a tous les clients
	 * @param message message � diffuser
	 */
	private void diffuser(String message) {    
		for (Joueur j : listJoueurs)
			j.envoyerMessage(message);
	}
	

	@Override
	public void run() {
		try {
			doit();
		} catch (IOException ie) {
			ie.printStackTrace();
		}
	}

	public void doit() throws IOException {

		String reception;
		// Boucle d'attente de connexion et dialogue
		while (true) {

			// Attente bloquante de demande de connexion
			Socket cs = ss.accept(); // Socket de discussion avec le client

			// Un BufferedReader permet de lire par ligne.
			BufferedReader br = new BufferedReader(new InputStreamReader(
					cs.getInputStream()));

			joueur = new Joueur(br.readLine(), new PrintWriter(new BufferedWriter(
					new OutputStreamWriter(cs.getOutputStream())), true));

			System.out.println("Demande de connexion de " + joueur.getLogin());
			if(connection(joueur)) {


				// Message d'acceuil
				joueur.envoyerMessage("Bienvenu " + joueur.getLogin() + "\n" +
										"Voici la grille de jeu : \n" +
										mSolver.recupererGrilleJeu());

				// Boucle de dialogue
				try {
					while (true) {
						reception = br.readLine();
						System.out.println("Message recu de " + joueur.getLogin() + " : "+ reception);

						if(reception.startsWith("/"))
							joueur.envoyerMessage(traiteCommande(reception.substring(1)));
						else if(reception.equals("end")) { //end n'est pas considere comme une commande
							deconnection(joueur);
							break;
						}
						else
							traiteReponse(reception);

						if (joueur.getErreur() == 3) {
							deconnection(joueur,"trop de mauvaise reponses.");
							break;
						}

					}
				}
				catch (SocketException e) {// un client s'est deconnecte inopinement
					deconnection(joueur);
					System.out.println("Le joueur " + joueur.getLogin() + " s'est deconnecte.");
				}
				finally {
					br.close();
					joueur.fermerPrinteWriter();
					cs.close();
				}
			}
			else {
				joueur.envoyerMessage("Pseudo deja existant. Connexion Impossible.");
				joueur.fermerPrinteWriter();
				cs.close();
				br.close();
			}
		}
	}

	/**
	 * Traite les commandes, commencent par "/"
	 * @param commande
	 * @return String representant le resultat de la commande
	 */
	private String traiteCommande(String commande) {
		if (commande.equals("point"))
			return "Mes points : " + joueur.getScore();
		else if(commande.equals("erreur"))
			return "Nombre d'erreurs envoyees : " + joueur.getErreur();
		else if (commande.equals("grille"))
			return mSolver.recupererGrilleJeu();
		else if(commande.equals("help")) {
			return "Aide : \n" +
					"Entrer /point pour connaitre votre score. \n" +
					"Entrer /erreur pour connaitre votre nombre d'erreurs enregistrees. \n" +
					"Entrer /grille pour connaitre la grille de jeu. \n" +
					"Entrer /listeJoueur pour connaitre la liste des joueurs. \n" +
					"Entrer /format pour connaitre le format des reponses \n" +
					"Entrer /codeErreur pour comprendre les codes d'erreurs. \n" +
					"Entrer /all pour envoyer un message a tout le monde \n" +
					"Entrer '/pseudo message' pour envoyer le message 'message' a 'pseudo' \n" +
					"Entrer end pour vous deconnecter. - Votre score sera remis a 0 si vous vous reconnectaient.";
		}
		else if(commande.equals("listeJoueur")){
			String res = "Les joueurs connectes sont : \n";
			return res += listeJoueur();
		}
		else if(commande.equals("codeErreur")) {
			return "Liste des codes d'erreurs : \n" +
					"Format du code : XYZRRR \n" +
					"RRR : votre reponse" +
					"X : de 0 a 3, nombre d'erreur a votre compteur \n" +
					"Y : 1 si l'erreur vous a incremente votre nombre d'erreur, 0 sinon \n" +
					"Z : code de l'erreur : \n" +
					"1 => bonne reponse, mais deja donne \n" +
					"2 => mauvaise reponse sur une case vide \n" +
					"3 => mauvaise reponse sur une case deja remplie \n" +
					"4 => la reponse est au mauvais format. /format pour connaitre le format \n";
		}
		else if (commande.equals("format")) {
			return "Format de la reponse : XYZ \n" +
					"X : ligne \n" +
					"Y : colonne \n" +
					"Z : valeur";

		}
		else if(commande.startsWith("all")) {
			int indexFinCommande = commande.indexOf(" ");
			if(indexFinCommande != -1) {
				diffuser("["+joueur.getLogin()+"] " + commande.substring(4));
				return "Message envoye";
			}
			else
				return "Message manquant.";
		}
		else {
			int indexFinPseudo = commande.indexOf(" ");

			if(indexFinPseudo != -1) {
				Joueur j = estPresent(commande.substring(0, indexFinPseudo));
				if(j!=null) {
					j.envoyerMessage("["+joueur.getLogin()+"] " +commande.substring(indexFinPseudo+1));
					return "Message envoye a " + j.getLogin();
				}
			}

			return "Commande inconnu.\n /help pour la liste des commandes.";
		}

	}

	/**
	 * Retourne la liste des joueurs avec leur score sous forme de chaine
	 * @return String representant la liste des joeurs
	 */
	private String listeJoueur() {
		String res="";
		for (Joueur j : listJoueurs) {
			res += j.getLogin() +"(" + j.getScore() + ") \n";
		}

		return res;
	}

	/**
	 * Indique si un joeur est present
	 * @param login login du joueur a chercher
	 * @return Le joueur s'il existe, null sinon
	 */
	private Joueur estPresent(String login) {
		for (Joueur j : listJoueurs) {
			if(j.getLogin().equals(login))
				return j;
		}
		return null;
	}

	/**
	 * Traite la reponse du client, si celle ci c'est pas une commande
	 * Reponse de la forme : LigneColonneValeur
	 * @param reception string a traiter
	 * @return chaine indiquant si la reponse est juste ou non
	 */
	private void traiteReponse(String reception) {
		if(reception.length() != 3) {
			joueur.incrementeErreur();
			joueur.envoyerMessage("Mauvaise reponse : " + joueur.getErreur() + "14"+reception);
			return;
		}
		String[] reponse = reception.split("");
		try {
			int x = Integer.parseInt(reponse[1]);
			int y = Integer.parseInt(reponse[2]);
			int valeur = Integer.parseInt(reponse[3]);

			switch (mSolver.ajouteValeur(x, y, valeur)) {
			case -2:
				joueur.incrementeErreur();
				joueur.envoyerMessage("Mauvaise reponse : " + joueur.getErreur() + "12"+reception);
				break;
			case -1:
				joueur.incrementeErreur();
				joueur.envoyerMessage("Mauvaise reponse : " + joueur.getErreur() + "13"+reception);
				break;
			case 0:
				joueur.envoyerMessage("Mauvaise reponse : " + joueur.getErreur() + "01"+reception);
				break;
			case 1:
				joueur.incrementeScore(1);
				diffuser(joueur.getLogin() + " a donnee une bonne reponse : " + reception);
				if(mSolver.estFini()) {
					joueur.incrementeScore(3);
					diffuser("Fin de partie : \n" + listeJoueur() + "\n");
					mSolver.initGrilleJeu();
					for (Joueur j : listJoueurs) {
						j.initPointsErreurs();
					}
					diffuser("Voici la nouvelle grille (vos points et compteur erreurs ont ete reinitialises) : \n" + mSolver.recupererGrilleJeu());
				}
				break;
			}
		}
		catch(NumberFormatException e) {
			joueur.incrementeErreur();
			joueur.envoyerMessage("014"+reception);
		}
	}
}

public class ServerMultiThread {

	public static void main(String[] args) throws IOException {
		if (args.length != 1) {
			System.out.println("Usage: java Server port");
			System.exit(0);
		}
		System.out.println("Ce serveur peut accepter 5 clients simultanement");
		int port; // port de connexion != port de discussion ac le client

		port = Integer.parseInt(args[0]);

		try {

			ServerSocket ss = new ServerSocket(port); // socket de service
			MonitorSolver ms = new MonitorSolver();
			System.out.println("Solution : \n" + ms.recupereGrilleSolution());

			Client c1 = new Client(ss, ms); // thread client1
			Client c2 = new Client(ss, ms); // thread client2
			Client c3 = new Client(ss, ms); // thread client3
			Client c4 = new Client(ss, ms); // thread client4
			Client c5 = new Client(ss, ms); // thread client5

			c1.start();
			c2.start();
			c3.start();
			c4.start();
			c5.start();

		}

		catch (BindException ie) {
			System.err.println("Le serveur est deja en cours d'executions");
		}
	}
}
